﻿// ------------------------------------------------------------------------------
//  © 2008 OpenUpon™.  All rights reserved.
// ------------------------------------------------------------------------------
using System.Text.RegularExpressions;

namespace CastUpon {
	public class HtmlSanitizer {
		/// <summary>
		/// Utility function to match a regex pattern: case, whitespace, and line insensitive
		/// </summary>
		private static bool IsMatch(string s, string pattern) {
			return Regex.IsMatch(s, pattern, RegexOptions.Singleline | RegexOptions.IgnoreCase |
			                                 RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture);
		}

		/// <summary>
		/// strips tag from the string
		/// </summary>
		private static string Strip(Match m, string html, ref int offset) {
			int loc = m.Index - offset;
			offset += m.Length;
			return html.Substring(0, loc) + html.Substring(loc + m.Length);
		}

		/// <summary>
		/// sanitize any potentially dangerous tags from the provided raw HTML input using 
		/// a whitelist based approach, leaving the "safe" HTML tags
		/// </summary>
		public static string SanitizeHtml(string html) {
			// Match all html tags
			MatchCollection tags = Regex.Matches(html, "<[^>]*?(>|$)", RegexOptions.Singleline);
			//      string whitelist =
			//        @"</?p>|<br\s?/?>|</?b>|</?strong>|</?i>|</?em>|</?s>|</?strike>|
			//          </?blockquote>|</?sub>|</?super>|</?h(1|2|3)>|</?pre>|<hr\s?/?>|
			//          </?code>|</?ul>|</?ol>|</?li>|
			//          </a>|<a[^>]+>|<img[^>]+/?>";
			string whitelist =
				@"</?p>|<br\s?/?>|</?b>|</?strong>|</?i>|</?em>|</?s>|</?strike>|
          </?blockquote>|</?sub>|</?super>|</?h(1|2|3)>|</?pre>|
          </?code>|</?ul>|</?ol>|</?li>|
          </a>|<a[^>]+>|<img[^>]+/?>";

			// loop through each matched tag
			string tag = "";
			int offset = 0;
			foreach(Match m in tags) {
				tag = m.ToString();
				if(!IsMatch(tag, whitelist)) {
					// not on our whitelist? I SAY GOOD DAY TO YOU, SIR. GOOD DAY!
					html = Strip(m, html, ref offset);
				}
				else if(IsMatch(tag, "^<a")) {
					// detailed <a> tag checking
					if(!IsMatch(tag,
					            @"<a\s
                  href=""(https?|ftp)://[^""]+""
                  (\stitle=""[^""]+"")?\s?>")) {
						html = Strip(m, html, ref offset);
					}
				}
				else if(IsMatch(tag, "^<img")) {
					// detailed <img> tag checking
					if(!IsMatch(tag,
					            @"<img\s
                  src=""https?://[^""]+""
                  (\swidth=""[^""]*"")?
                  (\sheight=""[^""]*"")?
                  (\salt=""[^""]*"")?
                  (\stitle=""[^""]*"")?
                  \s?/>")) {
						html = Strip(m, html, ref offset);
					}
				}
			}

			return html;
		}
	}
}